﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using System.Xml.Serialization;

namespace Rage.Mercenary
{
    /// <summary>List of Mercenary stats that are Additive</summary>
    public enum AdditiveStatMercenary : int
    {
        /// <summary>% Extra Pet Crit Chance</summary>
        BonusPetCritChance,
        /// <summary>Pet Stamina</summary>
        PetStamina,
        /// <summary>Pet Strength</summary>
        PetStrength,
        /// <summary>Pet Spirit</summary>
        PetSpirit,
        /// <summary>Pet Attack Power</summary>
        PetAttackPower,
        /// <summary>Bonus Critical Strike Chance on Serpent Sting</summary>
        BonusSerpentStingCritChance,
        /// <summary>Reduces the cast time of your Steady Shot and Cobra Shot by 0.2 sec.</summary>
        FourPieceTier11,
        /// <summary>You have a 10% chance from your autoshots to make your next shot or Kill Command cost no focus.</summary>
        FourPieceTier12,
        /// <summary>Unmitigated Agility used for Crit calculation</summary>
        BaseAgilityforCrit,
    }
    /// <summary>List of Mercenary stats that are Multiplicative</summary>
    public enum MultiplicativeStatMercenary : int
    {
        /// <summary>Bonus Ranged Attack Power Multiplier</summary>
        BonusRangedAttackPowerMultiplier,
        /// <summary>Bonus Pet Attack Power Multiplier</summary>
        BonusPetAttackPowerMultiplier,
        /// <summary>Bonus Focus Regeneration Multiplier</summary>
        BonusFocusRegenMultiplier,
        /// <summary>Your Steady Shot and Cobra Shot have a 10% chance to trigger a Flaming Arrow, dealing 80% instant weapon damage as Fire.</summary>
        BonusFireWeaponDamage,
        /// <summary>Bonus Haste Multiplier</summary>
        BonusHasteMultiplier,
    }
    /// <summary>List of Mercenary stats that are Inverse Multiplicative</summary>
    public enum InverseMultiplicativeStatMercenary : int { }
    /// <summary>List of Mercenary stats that do not Stack</summary>
    public enum NonStackingStatMercenary : int { }

    /// <summary>
    /// Mercenary custom implementation of the Stats object to expand it with Mercenary Specific variables
    /// </summary>
#if SILVERLIGHT
    public class StatsMercenary : Stats
#else
    public unsafe class StatsMercenary : Stats
#endif
    {
        #region Arrays, duplicated from Base to be populated by Mercenary Stats
        internal float[] _rawAdditiveMercenaryData = new float[AdditiveStatMercenaryCount];
        internal float[] _rawMultiplicativeMercenaryData = new float[MultiplicativeStatMercenaryCount];
        internal float[] _rawInverseMultiplicativeMercenaryData = new float[InverseMultiplicativeStatMercenaryCount];
        internal float[] _rawNoStackMercenaryData = new float[NonStackingStatMercenaryCount];

        internal int[] _sparseIndicesMercenary;
        internal int _sparseAdditiveMercenaryCount;
        internal int _sparseMultiplicativeMercenaryCount;
        internal int _sparseInverseMultiplicativeMercenaryCount;
        internal int _sparseNoStackMercenaryCount;

        /// <summary></summary>
        protected static PropertyInfo[] _propertyInfoCacheMercenary = null;
        /// <summary></summary>
        protected static List<PropertyInfo> _percentagePropertiesMercenary = new List<PropertyInfo>();
        /// <summary></summary>
        protected static int AdditiveStatMercenaryCount = 0;
        /// <summary></summary>
        protected static int MultiplicativeStatMercenaryCount = 0;
        /// <summary></summary>
        protected static int InverseMultiplicativeStatMercenaryCount = 0;
        /// <summary></summary>
        protected static int NonStackingStatMercenaryCount = 0;
        #endregion

        #region Sort-Of Overrides, they call the base function and then do the same thing with Mercenary Stats
        /// <summary>Dumps the cached values</summary>
        public new void Clear()
        {
            base.Clear();
            Array.Clear(_rawAdditiveMercenaryData, 0, _rawAdditiveMercenaryData.Length);
            Array.Clear(_rawMultiplicativeMercenaryData, 0, _rawMultiplicativeMercenaryData.Length);
            Array.Clear(_rawInverseMultiplicativeMercenaryData, 0, _rawInverseMultiplicativeMercenaryData.Length);
            Array.Clear(_rawNoStackMercenaryData, 0, _rawNoStackMercenaryData.Length);
            _rawSpecialEffectDataSize = 0;
        }

        /// <summary>Dumps the sparse data</summary>
        public new void InvalidateSparseData()
        {
            base.InvalidateSparseData();
            _sparseIndicesMercenary = null;
        }

        /// <summary>Generates the sparse data in base Stats class and in Mercenary Specific</summary>
        public new void GenerateSparseData()
        {
            base.GenerateSparseData();
            if (_sparseIndicesMercenary != null) return;
            lock (_rawAdditiveMercenaryData)
            {
                if (_sparseIndicesMercenary == null)
                {
                    List<int> indices = new List<int>();
                    _sparseAdditiveMercenaryCount = 0;
                    for (int i = 0; i < _rawAdditiveMercenaryData.Length; i++)
                    {
                        if (_rawAdditiveMercenaryData[i] != 0.0f)
                        {
                            _sparseAdditiveMercenaryCount++;
                            indices.Add(i);
                        }
                    }
                    _sparseMultiplicativeMercenaryCount = 0;
                    for (int i = 0; i < _rawMultiplicativeMercenaryData.Length; i++)
                    {
                        if (_rawMultiplicativeMercenaryData[i] != 0.0f)
                        {
                            _sparseMultiplicativeMercenaryCount++;
                            indices.Add(i);
                        }
                    }
                    _sparseInverseMultiplicativeMercenaryCount = 0;
                    for (int i = 0; i < _rawInverseMultiplicativeMercenaryData.Length; i++)
                    {
                        if (_rawInverseMultiplicativeMercenaryData[i] != 0.0f)
                        {
                            _sparseInverseMultiplicativeMercenaryCount++;
                            indices.Add(i);
                        }
                    }
                    _sparseNoStackMercenaryCount = 0;
                    for (int i = 0; i < _rawNoStackMercenaryData.Length; i++)
                    {
                        if (_rawNoStackMercenaryData[i] != 0.0f)
                        {
                            _sparseNoStackMercenaryCount++;
                            indices.Add(i);
                        }
                    }
                    _sparseIndicesMercenary = indices.ToArray();
                }
            }
        }

        /// <summary>Clones the stats object for duplication with separation</summary>
        public new StatsMercenary Clone()
        {
            StatsMercenary clone = (StatsMercenary)this.MemberwiseClone();
            StatsMercenary retVal = new StatsMercenary();
            retVal.Accumulate(base.Clone());
            retVal._rawAdditiveMercenaryData = (float[])clone._rawAdditiveMercenaryData.Clone();
            retVal._rawMultiplicativeMercenaryData = (float[])clone._rawMultiplicativeMercenaryData.Clone();
            retVal._rawInverseMultiplicativeMercenaryData = (float[])clone._rawInverseMultiplicativeMercenaryData.Clone();
            retVal._rawNoStackMercenaryData = (float[])clone._rawNoStackMercenaryData.Clone();
            return retVal;
        }

        /// <summary></summary>
        public static PropertyInfo[] PropertyInfoCacheMercenary { get { return _propertyInfoCacheMercenary; } }

        [XmlIgnore]
        public static String[] StatNamesMercenary
        {
            get
            {
                String[] names = new string[PropertyInfoCacheMercenary.Length];
                for (int i = 0; i < PropertyInfoCacheMercenary.Length; i++)
                {
                    names[i] = Extensions.DisplayName(PropertyInfoCacheMercenary[i]);
                }
                Array.Sort(names);
                return names;
            }
        }
        public IDictionary<PropertyInfo, float> ValuesMercenary(StatFilter filter)
        {
#if SILVERLIGHT
            Dictionary<PropertyInfo, float> dict = new Dictionary<PropertyInfo, float>();
#else
            SortedList<PropertyInfo, float> dict = new SortedList<PropertyInfo, float>(new PropertyComparer());
#endif
            foreach (PropertyInfo info in PropertyInfoCacheMercenary)
            {
                if (info.PropertyType.IsAssignableFrom(typeof(float)))
                {
                    float value = (float)info.GetValue(this, null);
                    if (filter(value))
                    {
                        dict[info] = value;
                    }
                }
            }
#if SILVERLIGHT
            //dict.OrderBy(kvp => kvp.Key, new PropertyComparer());
#endif
            return dict;
        }

        public override int GetHashCode()
        {
            return _rawAdditiveMercenaryData.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            return this == (obj as StatsMercenary);
        }
        public static bool operator ==(StatsMercenary x, StatsMercenary y)
        {
            if (ReferenceEquals(x, y) || (ReferenceEquals(x, null) && ReferenceEquals(y, null)))
                return true;
            if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
                return false;
            if (x._rawSpecialEffectDataSize > 0 || y._rawSpecialEffectDataSize > 0) return false;
            return ArrayUtils.AllEqual(x.rawAdditiveData, y.rawAdditiveData) &&
                ArrayUtils.AllEqual(x.rawMultiplicativeData, y.rawMultiplicativeData) &&
                ArrayUtils.AllEqual(x.rawNoStackData, y.rawNoStackData) &&
                ArrayUtils.AllEqual(x._rawAdditiveMercenaryData, y._rawAdditiveMercenaryData) &&
                ArrayUtils.AllEqual(x._rawMultiplicativeMercenaryData, y._rawMultiplicativeMercenaryData) &&
                ArrayUtils.AllEqual(x._rawNoStackMercenaryData, y._rawNoStackMercenaryData);
        }
        public static bool operator !=(StatsMercenary x, StatsMercenary y)
        {
            return !(x == y);
        }
        public static StatsMercenary operator *(StatsMercenary a, float b)
        {
            StatsMercenary c = new StatsMercenary();

            int i = c._rawAdditiveMercenaryData.Length;
            while (--i >= 0)
            {
                c._rawAdditiveMercenaryData[i] = a._rawAdditiveMercenaryData[i] * b;
            }
            i = c._rawMultiplicativeMercenaryData.Length;
            while (--i >= 0)
            {
                c._rawMultiplicativeMercenaryData[i] = a._rawMultiplicativeMercenaryData[i] * b;
            }
            i = c._rawInverseMultiplicativeMercenaryData.Length;
            while (--i >= 0)
            {
                c._rawInverseMultiplicativeMercenaryData[i] = a._rawInverseMultiplicativeMercenaryData[i] * b;
            }

            i = c._rawNoStackMercenaryData.Length;
            while (--i >= 0)
            {
                c._rawNoStackMercenaryData[i] = a._rawNoStackMercenaryData[i] * b;
            }
            return c;
        }

        public void Accumulate(StatsMercenary data)
        {
            #region Base
            if (data.sparseIndices != null)
            {
                int i = 0;
                for (int a = 0; a < data.sparseAdditiveCount; a++, i++)
                {
                    int index = data.sparseIndices[i];
                    rawAdditiveData[index] += data.rawAdditiveData[index];
                }
                for (int a = 0; a < data.sparseMultiplicativeCount; a++, i++)
                {
                    int index = data.sparseIndices[i];
                    rawMultiplicativeData[index] = (1 + rawMultiplicativeData[index]) * (1 + data.rawMultiplicativeData[index]) - 1;
                }
                for (int a = 0; a < data.sparseInverseMultiplicativeCount; a++, i++)
                {
                    int index = data.sparseIndices[i];
                    rawInverseMultiplicativeData[index] = 1 - (1 - rawInverseMultiplicativeData[index]) * (1 - data.rawInverseMultiplicativeData[index]);
                }
                for (int a = 0; a < data.sparseNoStackCount; a++, i++)
                {
                    int index = data.sparseIndices[i];
                    float value = data.rawNoStackData[index];
                    if (value > rawNoStackData[index]) rawNoStackData[index] = value;
                }
            }
            else
            {
                float[] add = data.rawAdditiveData;
                for (int i = 0; i < rawAdditiveData.Length; i++)
                {
                    rawAdditiveData[i] += add[i];
                }
                add = data.rawMultiplicativeData;
                for (int i = 0; i < rawMultiplicativeData.Length; i++)
                {
                    rawMultiplicativeData[i] = (1 + rawMultiplicativeData[i]) * (1 + add[i]) - 1;
                }
                add = data.rawInverseMultiplicativeData;
                for (int i = 0; i < rawInverseMultiplicativeData.Length; i++)
                {
                    rawInverseMultiplicativeData[i] = 1 - (1 - rawInverseMultiplicativeData[i]) * (1 - add[i]);
                }
                add = data.rawNoStackData;
                for (int i = 0; i < rawNoStackData.Length; i++)
                {
                    if (add[i] > rawNoStackData[i]) rawNoStackData[i] = add[i];
                }
            }
            #endregion
            #region Mercenary
            if (data._sparseIndicesMercenary != null)
            {
                int i = 0;
                for (int a = 0; a < data._sparseAdditiveMercenaryCount; a++, i++)
                {
                    int index = data._sparseIndicesMercenary[i];
                    _rawAdditiveMercenaryData[index] += data._rawAdditiveMercenaryData[index];
                }
                for (int a = 0; a < data._sparseMultiplicativeMercenaryCount; a++, i++)
                {
                    int index = data._sparseIndicesMercenary[i];
                    _rawMultiplicativeMercenaryData[index] = (1 + _rawMultiplicativeMercenaryData[index]) * (1 + data._rawMultiplicativeMercenaryData[index]) - 1;
                }
                for (int a = 0; a < data._sparseInverseMultiplicativeMercenaryCount; a++, i++)
                {
                    int index = data._sparseIndicesMercenary[i];
                    _rawInverseMultiplicativeMercenaryData[index] = 1 - (1 - _rawInverseMultiplicativeMercenaryData[index]) * (1 - data._rawInverseMultiplicativeMercenaryData[index]);
                }
                for (int a = 0; a < data._sparseNoStackMercenaryCount; a++, i++)
                {
                    int index = data._sparseIndicesMercenary[i];
                    float value = data._rawNoStackMercenaryData[index];
                    if (value > _rawNoStackMercenaryData[index]) _rawNoStackMercenaryData[index] = value;
                }
            }
            else
            {
                float[] add = data._rawAdditiveMercenaryData;
                for (int i = 0; i < _rawAdditiveMercenaryData.Length; i++)
                {
                    _rawAdditiveMercenaryData[i] += add[i];
                }
                add = data._rawMultiplicativeMercenaryData;
                for (int i = 0; i < _rawMultiplicativeMercenaryData.Length; i++)
                {
                    _rawMultiplicativeMercenaryData[i] = (1 + _rawMultiplicativeMercenaryData[i]) * (1 + add[i]) - 1;
                }
                add = data._rawInverseMultiplicativeMercenaryData;
                for (int i = 0; i < _rawInverseMultiplicativeMercenaryData.Length; i++)
                {
                    _rawInverseMultiplicativeMercenaryData[i] = 1 - (1 - _rawInverseMultiplicativeMercenaryData[i]) * (1 - add[i]);
                }
                add = data._rawNoStackMercenaryData;
                for (int i = 0; i < _rawNoStackMercenaryData.Length; i++)
                {
                    if (add[i] > _rawNoStackMercenaryData[i]) _rawNoStackMercenaryData[i] = add[i];
                }
            }
            #endregion
            if (data._rawSpecialEffectDataSize > 0)
            {
                EnsureSpecialEffectCapacity(_rawSpecialEffectDataSize + data._rawSpecialEffectDataSize);
                Array.Copy(data._rawSpecialEffectData, 0, _rawSpecialEffectData, _rawSpecialEffectDataSize, data._rawSpecialEffectDataSize);
                _rawSpecialEffectDataSize += data._rawSpecialEffectDataSize;
            }
        }

        public void Accumulate(StatsMercenary data, float weight)
        {
            // Check the root data:

            if (data == null)
            {
                return;
            }
            #region Base
            if (data.sparseIndices != null)
            {
                int i = 0;
                for (int a = 0; a < data.sparseAdditiveCount; a++, i++)
                {
                    int index = data.sparseIndices[i];
                    rawAdditiveData[index] += weight * data.rawAdditiveData[index];
                }
                for (int a = 0; a < data.sparseMultiplicativeCount; a++, i++)
                {
                    int index = data.sparseIndices[i];
                    rawMultiplicativeData[index] = (1 + rawMultiplicativeData[index]) * (1 + weight * data.rawMultiplicativeData[index]) - 1;
                }
                for (int a = 0; a < data.sparseInverseMultiplicativeCount; a++, i++)
                {
                    int index = data.sparseIndices[i];
                    rawInverseMultiplicativeData[index] = 1 - (1 - rawInverseMultiplicativeData[index]) * (1 - weight * data.rawInverseMultiplicativeData[index]);
                }
                for (int a = 0; a < data.sparseNoStackCount; a++, i++)
                {
                    int index = data.sparseIndices[i];
                    float value = weight * data.rawNoStackData[index];
                    if (value > rawNoStackData[index]) rawNoStackData[index] = value;
                }
            }
            else
            {
                float[] add = data.rawAdditiveData;
                for (int i = 0; i < rawAdditiveData.Length; i++)
                {
                    rawAdditiveData[i] += weight * add[i];
                }
                add = data.rawMultiplicativeData;
                for (int i = 0; i < rawMultiplicativeData.Length; i++)
                {
                    rawMultiplicativeData[i] = (1 + rawMultiplicativeData[i]) * (1 + weight * add[i]) - 1;
                }
                add = data.rawInverseMultiplicativeData;
                for (int i = 0; i < rawInverseMultiplicativeData.Length; i++)
                {
                    rawInverseMultiplicativeData[i] = 1 - (1 - rawInverseMultiplicativeData[i]) * (1 - weight * add[i]);
                }
                add = data.rawNoStackData;
                for (int i = 0; i < rawNoStackData.Length; i++)
                {
                    if (weight * add[i] > rawNoStackData[i]) rawNoStackData[i] = weight * add[i];
                }
            }
            #endregion
            #region Mercenary
            if (data._sparseIndicesMercenary != null)
            {
                int i = 0;
                for (int a = 0; a < data._sparseAdditiveMercenaryCount; a++, i++)
                {
                    int index = data._sparseIndicesMercenary[i];
                    _rawAdditiveMercenaryData[index] += weight * data._rawAdditiveMercenaryData[index];
                }
                for (int a = 0; a < data._sparseMultiplicativeMercenaryCount; a++, i++)
                {
                    int index = data._sparseIndicesMercenary[i];
                    _rawMultiplicativeMercenaryData[index] = (1 + _rawMultiplicativeMercenaryData[index]) * (1 + weight * data._rawMultiplicativeMercenaryData[index]) - 1;
                }
                for (int a = 0; a < data._sparseInverseMultiplicativeMercenaryCount; a++, i++)
                {
                    int index = data._sparseIndicesMercenary[i];
                    _rawInverseMultiplicativeMercenaryData[index] = 1 - (1 - _rawInverseMultiplicativeMercenaryData[index]) * (1 - weight * data._rawInverseMultiplicativeMercenaryData[index]);
                }
                for (int a = 0; a < data._sparseNoStackMercenaryCount; a++, i++)
                {
                    int index = data._sparseIndicesMercenary[i];
                    float value = weight * data._rawNoStackMercenaryData[index];
                    if (value > _rawNoStackMercenaryData[index]) _rawNoStackMercenaryData[index] = value;
                }
            }
            else
            {
                float[] add = data._rawAdditiveMercenaryData;
                for (int i = 0; i < _rawAdditiveMercenaryData.Length; i++)
                {
                    _rawAdditiveMercenaryData[i] += weight * add[i];
                }
                add = data._rawMultiplicativeMercenaryData;
                for (int i = 0; i < _rawMultiplicativeMercenaryData.Length; i++)
                {
                    _rawMultiplicativeMercenaryData[i] = (1 + _rawMultiplicativeMercenaryData[i]) * (1 + weight * add[i]) - 1;
                }
                add = data._rawInverseMultiplicativeMercenaryData;
                for (int i = 0; i < _rawInverseMultiplicativeMercenaryData.Length; i++)
                {
                    _rawInverseMultiplicativeMercenaryData[i] = 1 - (1 - _rawInverseMultiplicativeMercenaryData[i]) * (1 - weight * add[i]);
                }
                add = data._rawNoStackMercenaryData;
                for (int i = 0; i < _rawNoStackMercenaryData.Length; i++)
                {
                    if (weight * add[i] > _rawNoStackMercenaryData[i]) _rawNoStackMercenaryData[i] = weight * add[i];
                }
            }
            #endregion
            if (data._rawSpecialEffectDataSize > 0)
            {
                EnsureSpecialEffectCapacity(_rawSpecialEffectDataSize + data._rawSpecialEffectDataSize);
                Array.Copy(data._rawSpecialEffectData, 0, _rawSpecialEffectData, _rawSpecialEffectDataSize, data._rawSpecialEffectDataSize);
                _rawSpecialEffectDataSize += data._rawSpecialEffectDataSize;
            }
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();

            foreach (PropertyInfo info in PropertyInfoCache)
            {
                float value = (float)info.GetValue(this, null);
                if (value != 0)
                {
                    if (IsPercentage(info)) { value *= 100; }
                    value = (float)Math.Round(value * 100f) / 100f;
                    sb.AppendFormat("{0}{1}, ", value, Extensions.DisplayName(info));
                }
            }
            foreach (PropertyInfo info in PropertyInfoCacheMercenary)
            {
                float value = (float)info.GetValue(this, null);
                if (value != 0)
                {
                    if (IsPercentage(info)) { value *= 100; }
                    value = (float)Math.Round(value * 100f) / 100f;
                    sb.AppendFormat("{0}{1}, ", value, Extensions.DisplayName(info));
                }
            }
            foreach (SpecialEffect effect in SpecialEffects())
            {
                sb.AppendFormat("{0}, ", effect);
            }

            return sb.ToString().TrimEnd(' ', ',');
        }
        #endregion

        static StatsMercenary()
        {
            List<PropertyInfo> items = new List<PropertyInfo>();
            List<PropertyInfo> itemsMercenary = new List<PropertyInfo>();

            foreach (PropertyInfo info in typeof(StatsMercenary).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.ExactBinding))
            {
                if (info.PropertyType.IsAssignableFrom(typeof(float)))
                {
                    itemsMercenary.Add(info);
                }
            }
            _propertyInfoCacheMercenary = itemsMercenary.ToArray();

            foreach (PropertyInfo info in _propertyInfoCacheMercenary)
            {
                if (info.GetCustomAttributes(typeof(PercentageAttribute), false).Length > 0)
                {
                    _percentagePropertiesMercenary.Add(info);
                }
            }

            AdditiveStatMercenaryCount = EnumHelper.GetValues(typeof(AdditiveStatMercenary)).Length;
            MultiplicativeStatMercenaryCount = EnumHelper.GetValues(typeof(MultiplicativeStatMercenary)).Length;
            InverseMultiplicativeStatMercenaryCount = EnumHelper.GetValues(typeof(InverseMultiplicativeStatMercenary)).Length;
            NonStackingStatMercenaryCount = EnumHelper.GetValues(typeof(NonStackingStatMercenary)).Length;
        }

        #region ===== Additive Stats ==================
        /// <summary>% Extra Pet Crit Chance</summary>
        [DefaultValueAttribute(0f)]
        [Percentage]
        [DisplayName("% Extra Pet Crit Chance")]
        [Category("Mercenary")]
        public float BonusPetCritChance
        {
            get { return _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.BonusPetCritChance]; }
            set { _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.BonusPetCritChance] = value; }
        }
        /// <summary>Pet Stamina</summary>
        [DefaultValueAttribute(0f)]
        [DisplayName("Pet Stamina")]
        [Category("Mercenary")]
        public float PetStamina
        {
            get { return _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.PetStamina]; }
            set { _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.PetStamina] = value; }
        }
        /// <summary>Pet Strength</summary>
        [DefaultValueAttribute(0f)]
        [DisplayName("Pet Strength")]
        [Category("Mercenary")]
        public float PetStrength
        {
            get { return _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.PetStrength]; }
            set { _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.PetStrength] = value; }
        }
        /// <summary>Pet Spirit</summary>
        [DefaultValueAttribute(0f)]
        [DisplayName("Pet Spirit")]
        [Category("Mercenary")]
        public float PetSpirit
        {
            get { return _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.PetSpirit]; }
            set { _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.PetSpirit] = value; }
        }
        /// <summary>Pet Attack Power</summary>
        [DefaultValueAttribute(0f)]
        [DisplayName("Pet Attack Power")]
        [Category("Mercenary")]
        public float PetAttackPower
        {
            get { return _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.PetAttackPower]; }
            set { _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.PetAttackPower] = value; }
        }
        /// <summary>Bonus Critical Strike Chance on Serpent Sting</summary>
        [DefaultValueAttribute(0f)]
        [Percentage]
        [DisplayName("% Bonus Serpent Sting Crit Chance")]
        [Category("Mercenary")]
        public float BonusSerpentStingCritChance
        {
            get { return _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.BonusSerpentStingCritChance]; }
            set { _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.BonusSerpentStingCritChance] = value; }
        }
        /// <summary>Reduces the cast time of your Steady Shot and Cobra Shot by 0.2 sec.</summary>
        [DefaultValueAttribute(0f)]
        [DisplayName("Mercenary 4-Piece Tier 11")]
        [Category("Mercenary")]
        public float FourPieceTier11
        {
            get { return _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.FourPieceTier11]; }
            set { _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.FourPieceTier11] = value; }
        }
        /// <summary>You have a 10% chance from your autoshots to make your next shot or Kill Command cost no focus.</summary>
        [DefaultValueAttribute(0f)]
        [DisplayName("Mercenary 4-Piece Tier 12")]
        [Category("Mercenary")]
        public float FourPieceTier12
        {
            get { return _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.FourPieceTier12]; }
            set { _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.FourPieceTier12] = value; }
        }
        [DefaultValueAttribute(0f)]
        [DisplayName("BaseAgilityForCrit")]
        [Category("Mercenary")]
        public float BaseAgilityforCrit
        {
            get { return _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.BaseAgilityforCrit]; }
            set { _rawAdditiveMercenaryData[(int)AdditiveStatMercenary.BaseAgilityforCrit] = value; }
        }
        #endregion

        #region ===== Multiplicative Stats ============
        /// <summary>Bonus Ranged Attack Power Multiplier</summary>
        [DefaultValueAttribute(0f)]
        [Percentage]
        [DisplayName("% Bonus Ranged AP")]
        [Category("Mercenary")]
        public float BonusRangedAttackPowerMultiplier
        {
            get { return _rawAdditiveMercenaryData[(int)MultiplicativeStatMercenary.BonusRangedAttackPowerMultiplier]; }
            set { _rawAdditiveMercenaryData[(int)MultiplicativeStatMercenary.BonusRangedAttackPowerMultiplier] = value; }
        }
        /// <summary>Bonus Pet Attack Power Multiplier</summary>
        [DefaultValueAttribute(0f)]
        [Percentage]
        [DisplayName("% Bonus Pet AP")]
        [Category("Mercenary")]
        public float BonusPetAttackPowerMultiplier
        {
            get { return _rawAdditiveMercenaryData[(int)MultiplicativeStatMercenary.BonusPetAttackPowerMultiplier]; }
            set { _rawAdditiveMercenaryData[(int)MultiplicativeStatMercenary.BonusPetAttackPowerMultiplier] = value; }
        }
        /// <summary>Bonus Focus Regen Multiplier</summary>
        [DefaultValueAttribute(0f)]
        [Percentage]
        [DisplayName("% Bonus Focus Regen")]
        [Category("Mercenary")]
        public float BonusFocusRegenMultiplier
        {
            get { return _rawAdditiveMercenaryData[(int)MultiplicativeStatMercenary.BonusFocusRegenMultiplier]; }
            set { _rawAdditiveMercenaryData[(int)MultiplicativeStatMercenary.BonusFocusRegenMultiplier] = value; }
        }
        /// <summary>Your Steady Shot and Cobra Shot have a 10% chance to trigger a Flaming Arrow, dealing 80% instant weapon damage as Fire.</summary>
        [DefaultValueAttribute(0f)]
        [Percentage]
        [DisplayName("% Bonus Fire Weapon Damage")]
        [Category("Mercenary")]
        public float BonusFireWeaponDamage
        {
            get { return _rawAdditiveMercenaryData[(int)MultiplicativeStatMercenary.BonusFireWeaponDamage]; }
            set { _rawAdditiveMercenaryData[(int)MultiplicativeStatMercenary.BonusFireWeaponDamage] = value; }
        }
        /// <summary>Bonus Haste Multiplier</summary>
        [DefaultValueAttribute(0f)]
        [Percentage]
        [DisplayName("% Bonus Haste")]
        [Category("Mercenary")]
        public float BonusHasteMultiplier
        {
            get { return _rawAdditiveMercenaryData[(int)MultiplicativeStatMercenary.BonusHasteMultiplier]; }
            set { _rawAdditiveMercenaryData[(int)MultiplicativeStatMercenary.BonusHasteMultiplier] = value; }
        }
        #endregion
    }
}
